// -*- mode:objc -*-
// $Id: ITTerminal.m,v 1.216 2003/08/13 05:03:54 sgehrman Exp $
//
/*
 **  ITTerminal.m
 **
 **  Copyright (c) 2002, 2003
 **
 **  Author: Fabian, Ujwal S. Sathyam
 **	     Initial code by Kiichi Kusama
 **
 **  Project: iTerm
 **
 **  Description: Session and window controller for iTerm.
 **
 **  This program is free software; you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation; either version 2 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program; if not, write to the Free Software
 **  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#import "iTerm.h"
#import "ITTerminal.h"
#import "NSStringITerm.h"
#import "PTYSession.h"
#import "VT100Screen.h"
//#import "NSTabView.h"
//#import "NSTabViewItem.h"
#import "ITPreferences.h"
#import "PTYTask.h"
#import "PTYTextView.h"
#import "VT100Terminal.h"
#import "VT100Screen.h"
#import "PTYSession.h"
#import "ITSessionMgr.h"
#import "ITTerminalView.h"
#import "ITSessionDesc.h"

@interface ITTerminal (Private)
- (void)setupSession:(PTYSession *)aSession
        sessionDesc:(ITSessionDesc*)sessionDesc
               title:(NSString *)title;

+ (void)breakDown:(NSString *)cmdl cmdPath:(NSString **)cmd cmdArgs:(NSArray **)path;
- (void)startCommand:(ITSessionDesc*)desc;
@end

@implementation ITTerminal

// used to load the fonts inside the framework
+ (void)initialize;
{    
    [super initialize];
}

- (id)initWithFrame:(NSRect)frame
{    
    self = [super init];
    
    _sessionMgr = [[ITSessionMgr alloc] init];
    
    tabViewDragOperationInProgress = NO;    
    
    _terminalView = [[ITTerminalView alloc] initWithFrame:frame tabViewType:NSLeftTabsBezelBorder];
	
    return self;    
}

- (void)dealloc
{
    [_terminalView release];
    
    [_sessionMgr release];
    _sessionMgr = nil;
    
    [super dealloc];
}

- (ITTerminalView*)terminalView;
{
    return _terminalView;
}

// returns an array of NSNumbers
- (NSArray*)ttyPIDs:(BOOL)currentSessionOnly;
{
    NSMutableArray* result = [NSMutableArray array];
    NSEnumerator *enumerator = [[_sessionMgr sessionList] objectEnumerator];
    PTYSession *session;
    
    if (currentSessionOnly)
        [result addObject:[NSNumber numberWithInt:[[[_sessionMgr currentSession] shell] pid]]];
    else
    {
        while (session = [enumerator nextObject])
            [result addObject:[NSNumber numberWithInt:[[session shell] pid]]];
    }
    
    return result;
}

- (NSArray*)ttyFileDescs:(BOOL)currentSessionOnly;
{
    NSMutableArray* result = [NSMutableArray array];
    NSEnumerator *enumerator = [[_sessionMgr sessionList] objectEnumerator];
    PTYSession *session;
    
    if (currentSessionOnly)    
        [result addObject:[NSNumber numberWithInt:[[[_sessionMgr currentSession] shell] fileDesc]]];
    else
    {
        while (session = [enumerator nextObject])
            [result addObject:[NSNumber numberWithInt:[[session shell] fileDesc]]];
    }
    
    return result;
}

// returns an array of NSStrings
- (NSArray*)ttyNames:(BOOL)currentSessionOnly;
{
    NSMutableArray* result = [NSMutableArray array];
    NSEnumerator *enumerator = [[_sessionMgr sessionList] objectEnumerator];
    PTYSession *session;
    
    if (currentSessionOnly)
        [result addObject:[[[_sessionMgr currentSession] shell] tty]];
    else
    {        
        while (session = [enumerator nextObject])
            [result addObject:[[session shell] tty]];
    }
    
    return result;
}

- (ITSessionMgr*)sessionMgr;
{
    return _sessionMgr;
}

- (void) selectSession: (PTYSession *) aSession
{    
    [[_terminalView tabView] selectTabViewItemWithIdentifier: aSession];
    if ([_sessionMgr currentSession]) 
        [[_sessionMgr currentSession] resetStatus];
    
    [_sessionMgr setCurrentSession:aSession];

    [[_sessionMgr currentSession] setLabelAttribute];
}

- (void)selectSessionAtIndexAction:(id)sender
{
    [self selectSessionAtIndex:[sender tag]];
}

- (void)selectSessionAtIndex: (int) sessionIndex
{
    if (sessionIndex < 0 || sessionIndex >= [_sessionMgr numberOfSessions]) 
        return;

    [self selectSession:[_sessionMgr sessionAtIndex:sessionIndex]];
}

- (void)insertSession:(PTYSession *)aSession;
{
    [self insertSession:aSession atIndex:[_sessionMgr numberOfSessions]];
}

- (void)insertSession: (PTYSession *) aSession atIndex: (int) index
{
    NSTabViewItem *aTabViewItem;

    if(aSession == nil)
	return;

    if (![_sessionMgr containsSession:aSession])
    {
	[aSession setParent:self];
        
	if ([_sessionMgr numberOfSessions] == 0)
	{
	    // Tell us whenever something happens with the tab view
	    [[_terminalView tabView] setDelegate: self];
	}	

	// create a new tab
	aTabViewItem = [[NSTabViewItem alloc] initWithIdentifier: aSession];
	NSParameterAssert(aTabViewItem != nil);
	[aTabViewItem setLabel: [aSession name]];
        [aTabViewItem setView: [aSession scrollView]];
        [aTabViewItem setInitialFirstResponder:[aSession TEXTVIEW]];
	[[aSession scrollView] setVerticalPageScroll: 0.0];
	[[_terminalView tabView] insertTabViewItem: aTabViewItem atIndex: index];

        [aTabViewItem release];
	[aSession setTabViewItem: aTabViewItem];
	[self selectSessionAtIndex:index];

	if ([[_terminalView tabView] numberOfTabViewItems] == 1)
	    [[aSession TEXTVIEW] scrollRangeToVisible: NSMakeRange([[[aSession TEXTVIEW] string] length] - 1, 1)];
    }
}

- (void) closeSession: (PTYSession*) aSession
{
    int i;
    int n=[_sessionMgr numberOfSessions];
    
    if((_sessionMgr == nil) || ![_sessionMgr containsSession:aSession])
        return;
    
    for(i=0;i<n;i++) 
    {
        if ([_sessionMgr sessionAtIndex:i] == aSession)
        {
            NSWindow* window = [_terminalView window];

            // make sure we set the firstResponder to nil if this text view is first responder, otherwise it can bomb
            if ([window firstResponder] == [aSession TEXTVIEW])
                [window makeFirstResponder:nil];
            
            // remove from tabview before terminating!! Terminating will
            // set the internal tabview object in the session to nil.
	    [aSession retain];
            [[_terminalView tabView] removeTabViewItem: [aSession tabViewItem]];
            [aSession terminate];
	    [aSession release];
	                
            // the above code removes the item and resets the currentSessionIndex
            [self selectSessionAtIndex:[_sessionMgr currentSessionIndex]];
                        
            break;
        }
    }
}

- (IBAction) closeCurrentSession: (id) sender
{
    if(_sessionMgr == nil)
        return;
        
    [self closeSession:[_sessionMgr currentSession]];
}

- (NSString *) currentSessionName
{
    return ([[_sessionMgr currentSession] name]);
}

- (void) setCurrentSessionName: (NSString *) theSessionName
{
    NSMutableString *title = [NSMutableString string];
    
    if(theSessionName != nil)
    {
        [[_sessionMgr currentSession] setName: theSessionName];
        [[[_sessionMgr currentSession] tabViewItem] setLabel: theSessionName];
    }
    else 
    {
        NSString *progpath = [NSString stringWithFormat: @"%@ #%d", [[[[[_sessionMgr currentSession] shell] path] pathComponents] lastObject], [_sessionMgr currentSessionIndex]];

        if ([[_sessionMgr currentSession] exited])
            [title appendString:@"Finish"];
        else
            [title appendString:progpath];

        [[_sessionMgr currentSession] setName: title];
        [[[_sessionMgr currentSession] tabViewItem] setLabel: title];
    }
}

- (PTYSession *) currentSession
{
    return [_sessionMgr currentSession];
}

- (int) currentSessionIndex
{
    return ([_sessionMgr currentSessionIndex]);
}

- (void)startProgram:(NSString *)program
{
    [[_sessionMgr currentSession] startProgram:program
	     arguments:[NSArray array]
           environment:[NSDictionary dictionary]];
}

- (void)startProgram:(NSString *)program arguments:(NSArray *)prog_argv
{
    [[_sessionMgr currentSession] startProgram:program
             arguments:prog_argv
           environment:[NSDictionary dictionary]];
}

- (void)startProgram:(NSString *)program
                  arguments:(NSArray *)prog_argv
                environment:(NSDictionary *)prog_env
{
    [[_sessionMgr currentSession] startProgram:program
                          arguments:prog_argv
                        environment:prog_env];
}

- (void)clearBuffer:(id)sender
{
    [[_sessionMgr currentSession] clearBuffer];
}

- (void)clearScrollbackBuffer:(id)sender
{
    [[_sessionMgr currentSession] clearScrollbackBuffer];
}

- (IBAction)logStart:(id)sender
{
    if (![[[_sessionMgr currentSession] shell] logging]) [[_sessionMgr currentSession] logStart];
}

- (IBAction)logStop:(id)sender
{
    if ([[[_sessionMgr currentSession] shell] logging]) [[_sessionMgr currentSession] logStop];
}

- (BOOL)validateMenuItem:(NSMenuItem *)item
{
    BOOL logging = [[[_sessionMgr currentSession] shell] logging];
    BOOL result = YES;

    if ([item action] == @selector(logStart:)) {
        result = logging == YES ? NO:YES;
    }
    else if ([item action] == @selector(logStop:)) {
        result = logging == NO ? NO:YES;
    }
    return result;
}

// NSTabView
- (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
    PTYSession *aSession;
    
    aSession = [tabViewItem identifier];
    
    if ([_sessionMgr currentSession]) 
        [[_sessionMgr currentSession] resetStatus];
    
    [_sessionMgr setCurrentSession:aSession];
}

- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{    
    [[_sessionMgr currentSession] setLabelAttribute];
}

- (void)tabView:(NSTabView *)tabView willRemoveTabViewItem:(NSTabViewItem *)tabViewItem
{
    PTYSession *aSession = [tabViewItem identifier];

    if([_sessionMgr containsSession: aSession] && [aSession isKindOfClass: [PTYSession class]])
	[_sessionMgr removeSession: aSession];
}

- (void)tabView:(NSTabView *)tabView willAddTabViewItem:(NSTabViewItem *)tabViewItem
{
    [self tabView: tabView willInsertTabViewItem: tabViewItem atIndex: [tabView numberOfTabViewItems]];
}

- (void)tabView:(NSTabView *)tabView willInsertTabViewItem:(NSTabViewItem *)tabViewItem atIndex: (int) index
{
    if(tabView == nil || tabViewItem == nil || index < 0)
	return;
    
    PTYSession *aSession = [tabViewItem identifier];

    if(![_sessionMgr containsSession: aSession] && [aSession isKindOfClass: [PTYSession class]])
    {
	[aSession setParent: self];
	
        [_sessionMgr insertSession: aSession atIndex: index];
    }
}

- (void)tabViewWillPerformDragOperation:(NSTabView *)tabView
{
    tabViewDragOperationInProgress = YES;
}

- (void)tabViewDidPerformDragOperation:(NSTabView *)tabView
{
    tabViewDragOperationInProgress = NO;
    [self tabViewDidChangeNumberOfTabViewItems: tabView];
}

- (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)tabView
{
    if(tabViewDragOperationInProgress == YES)
	return;
    
    [_sessionMgr setCurrentSessionIndex:[[_terminalView tabView] indexOfTabViewItem: [[_terminalView tabView] selectedTabViewItem]]];

    if ([[_terminalView tabView] numberOfTabViewItems] == 1)
    {
	if([[ITPreferences sharedInstance] hideTab])
	{
            PTYSession *aSession = [[[_terminalView tabView] tabViewItemAtIndex: 0] identifier];

            [_terminalView setTabViewType: NSNoTabsBezelBorder];
	    [[aSession TEXTVIEW] scrollRangeToVisible: NSMakeRange([[[aSession TEXTVIEW] string] length] - 1, 1)];
	}
	else
	    [_terminalView setTabViewType: [[ITPreferences sharedInstance] tabViewType]];

    }
    else if([[_terminalView tabView] numberOfTabViewItems] == 2)
	[_terminalView setTabViewType: [[ITPreferences sharedInstance] tabViewType]];
}

- (void)tabViewContextualMenu: (NSEvent *)theEvent menu: (NSMenu *)theMenu
{
    NSMenuItem *aMenuItem;
    NSPoint windowPoint, localPoint;

    if((theEvent == nil) || (theMenu == nil))
	return;

    windowPoint = [[_terminalView window] convertScreenToBase: [NSEvent mouseLocation]];
    localPoint = [[_terminalView tabView] convertPoint: windowPoint fromView: nil];

    if([[_terminalView tabView] tabViewItemAtPoint:localPoint] == nil)
	return;

    [theMenu addItem: [NSMenuItem separatorItem]];

    // add tasks
    aMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedStringFromTableInBundle(@"Close",@"iTerm", [NSBundle bundleForClass: [self class]], @"Close Session") action:@selector(closeTabContextualMenuAction:) keyEquivalent:@""];
    [aMenuItem setRepresentedObject: [[[_terminalView tabView] tabViewItemAtPoint:localPoint] identifier]];
    [theMenu addItem: aMenuItem];
    [aMenuItem release];
    if([_sessionMgr numberOfSessions] > 1)
    {
	aMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedStringFromTableInBundle(@"Move to new window",@"iTerm", [NSBundle bundleForClass: [self class]], @"Move session to new window") action:@selector(moveTabToNewWindowContextualMenuAction:) keyEquivalent:@""];
	[aMenuItem setRepresentedObject: [[[_terminalView tabView] tabViewItemAtPoint:localPoint] identifier]];
	[theMenu addItem: aMenuItem];
	[aMenuItem release];
    }
}

// closes a tab
- (void) closeTabContextualMenuAction: (id) sender
{
    [self closeSession: [sender representedObject]];
}

// moves a tab with its session to a new window
- (void) moveTabToNewWindowContextualMenuAction: (id) sender
{
    ITTerminal *term;
    PTYSession *aSession;
    NSTabViewItem *aTabViewItem;

    // grab the referenced session
    aSession = [sender representedObject];
    if(aSession == nil)
	return;

    // create a new terminal window
    term = [[[ITTerminal alloc] init] autorelease];
    if(term == nil)
	return;

    // If this is the current session, make previous one active.
    if(aSession == [_sessionMgr currentSession])
	[self selectSessionAtIndex: ([_sessionMgr currentSessionIndex] - 1)];

    aTabViewItem = [aSession tabViewItem];

    // temporarily retain the tabViewItem
    [aTabViewItem retain];

    // remove from our window
    [[_terminalView tabView] removeTabViewItem: aTabViewItem];

    // add the session to the new terminal
    [term insertSession: aSession atIndex: 0];

    // release the tabViewItem
    [aTabViewItem release];
}

// Executes an addressbook command in new window or tab
- (void)openSessionWithDesc:(ITSessionDesc*)desc;
{
    // turn off window flushing to avoid flicker
    BOOL restore = NO;
    if (![[_terminalView window] isFlushWindowDisabled])
    {
        restore = YES;
        [[_terminalView window] disableFlushWindow];
    }
        
    if (!desc)
        desc = [ITSessionDesc defaultDesc];
    
    // Initialize a new session
    PTYSession *aSession = [[[PTYSession alloc] init] autorelease];
    
    [self setupSession:aSession sessionDesc:desc title:nil];
    [self insertSession:aSession];
                
    // Start the command
    [self startCommand:desc];
    [self setCurrentSessionName:[desc name]];
    
    if (restore)
        [[_terminalView window] enableFlushWindow];    
}

@end

@implementation ITTerminal (Private)

- (void)setupSession:(PTYSession *)aSession
  sessionDesc:(ITSessionDesc*)sessionDesc
               title:(NSString *)title;
{
    // Init the rest of the session
    [aSession setParent: self];
    [aSession initScreen: [[_terminalView tabView] contentRect]];
    
    // set some default parameters
    [aSession setPreferencesFromSessionDesc:sessionDesc];
    
    // set the srolling
    [[aSession scrollView] setVerticalLineScroll: [[aSession SCREEN] characterSize].height];
    [[aSession scrollView] setVerticalPageScroll: [[aSession TEXTVIEW] frame].size.height];
    
    // Set the bell option
    [VT100Screen setPlayBellFlag: ![[ITPreferences sharedInstance] silenceBell]];
        
    // assign terminal and task objects
    [[aSession SCREEN] setTerminal:[aSession TERMINAL]];
    [[aSession SCREEN] setShellTask:[aSession shell]];
    [[aSession TEXTVIEW] setDataSource: [aSession SCREEN]];
    [[aSession SCREEN] setTextStorage:[[aSession TEXTVIEW] textStorage]];
    
    [[aSession SCREEN] setScrollback:[[ITPreferences sharedInstance] numScrollbackLines]];
    
    // initialize the screen
    [[aSession SCREEN] initScreen];
    
    [aSession startSession:title blinkCursor:[[ITPreferences sharedInstance] blinkCursor]];
}

- (void)startCommand:(ITSessionDesc*)desc
{
    NSString *cmd;
    NSArray *arg;
    NSString* directory = [desc directory];
    
    [ITTerminal breakDown:[desc command] cmdPath:&cmd cmdArgs:&arg];
    
    // if command is "login", but directory is not home, must change the command to the default shell
    // the default login ignores the directory
    if ([cmd isEqualToString:@"login"])
    {
        if (![directory isEqualToString:[@"~" stringByExpandingTildeInPath]])
        {
            // get the default command
            cmd = [[ITSessionDesc defaultShell] lastPathComponent];
            arg = [NSArray array];
        }
    }
    
    [self startProgram:cmd arguments:arg environment:[NSDictionary dictionaryWithObject:directory forKey:@"PWD"]];
}

+ (void)breakDown:(NSString *)cmdl cmdPath:(NSString **)cmd cmdArgs:(NSArray **)path;
{
    int i,j,k,qf;
    char tmp[100];
    const char *s;
    NSMutableArray *p;
    
    p = [[NSMutableArray alloc] init];
    
    s = [cmdl UTF8String];
    
    i= j = qf= 0;
    k = -1;
    
    while (i <= strlen(s)) 
    {
        if (qf) 
        {
            if (s[i] == '\"')
                qf = 0;
            else
                tmp[j++] = s[i];
        }
        else 
        {
            if (s[i] == '\"')
                qf = 1;
            else if (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == 0)
            {
                tmp[j] = 0;
                if (k == -1)
                    *cmd = [NSString stringWithUTF8String:tmp];
                else
                    [p addObject:[NSString stringWithUTF8String:tmp]];
                
                j=0;
                k++;
                
                while (s[i+1] == ' ' || s[i+1] == '\t' || s[i+1] == '\n' || s[i+1]==0)
                    i++;
            }
            else
                tmp[j++] = s[i];
        }
        
        i++;
    }
    
    *path = [NSArray arrayWithArray:p];
    [p release];
}

@end
